package com.danicsoft.daide.token;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import com.danicsoft.daide.token.TokenMap.Parameters;

/**
 * <p>
 * A TokenGroup is essentially a list of tokens. It contains some convenience
 * methods specific to tokens, but it is essentially a list of tokens. In DPP
 * syntax, a TokenGroup is a collection of tokens all within parenthesis. So, if
 * we had (AUS, ENG, FRA, GER, ITA, RUS, TUR) then the TokenGroup that
 * represents that would contain tokens representing AUS, ENG, FRA, GER, ITA,
 * RUS and TUR.
 * </p>
 * <p>
 * A TokenGroup is itself an iterator. This means that the iterator it contains
 * can only be iterated over once. If you wish to iterate over a TokenGroup
 * multiple times, you must use the <code>{@link #reset()}</code> function.
 * </p>
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class TokenGroup extends Token implements Iterable<Token>, ListIterator<Token> {

	@Override
	public byte getType() {
		return TokenMap.GROUP;
	}

	/**
	 * The list of tokens that this TokenGroup contains.
	 */
	private List<Token> myList;

	/**
	 * The current index of the iterator. The next call to {@link #next()} will
	 * return the token at the given index.
	 */
	private int curIndex = 0;

	/**
	 * Create a new <code>TokenGroup</code>.
	 */
	public TokenGroup() {
		super((byte) 0);
		myList = new ArrayList<Token>();
	}

	public void add(Token token) {
		myList.add(token);
	}

	/**
	 * Removes the given token from the list
	 * 
	 * @param token
	 *        The token to remove.
	 */
	public void remove(Token token) {
		myList.remove(token);
	}

	@Override
	public Object getData() {
		return myList;
	}

	@Override
	public void output(OutputStream output) throws IOException {

		for (Token token : myList) {
			if (token.isGroup()) {
				TokenMap.getParenthesisToken(true).output(output);
			}
			token.output(output);
			if (token.isGroup()) {
				TokenMap.getParenthesisToken(false).output(output);
			}
		}

	}

	@Override
	public String toString() {
		StringBuffer sBuf = new StringBuffer();
		boolean wasGroup = true;
		boolean first = true;
		for (Token tok : myList) {

			if ((!wasGroup || !tok.isGroup()) && !first) {
				sBuf.append(' ');
			}

			if (tok.isGroup()) {
				sBuf.append('(');
			}

			sBuf.append(tok.toString());

			if (tok.isGroup()) {
				sBuf.append(')');
			}
			wasGroup = tok.isGroup();
			first = false;

		}

		return sBuf.toString();
	}

	public Iterator<Token> iterator() {
		return this;
	}

	public boolean hasNext() {
		return curIndex < myList.size();
	}

	public Token next() {
		if (hasNext()) {
			int rIndex = curIndex;
			curIndex++;
			return myList.get(rIndex);
		}
		throw new NoSuchElementException();
	}

	public void remove() {
		remove(myList.get(curIndex));

	}

	@Override
	public boolean isGroup() {
		return true;
	}

	public boolean hasPrevious() {
		return curIndex > 0;
	}

	public int nextIndex() {
		return curIndex + 1;
	}

	public Token previous() {
		if (hasPrevious()) {
			return myList.get(--curIndex);
		}
		throw new NoSuchElementException();
	}

	public int previousIndex() {
		return curIndex - 1;
	}

	public void set(Token o) {
		myList.set(curIndex, o);

	}

	/**
	 * Resets the iterator to the beginning.
	 */
	public void reset() {
		curIndex = 0;
	}

	@Override
	public Token clone() {
		TokenGroup cGroup = new TokenGroup();
		for (Token tok : myList) {
			cGroup.myList.add(tok.clone());
		}
		return cGroup;
	}

	@Override
	public int hashCode() {
		int total = 0;
		for (Token tok : myList) {
			total += tok.hashCode();
		}
		return total;
	}

	/**
	 * Insert an ERR token immediately before the last token accessed.
	 * 
	 * @return True if this TokenGroup has been accessed.
	 */
	public boolean insertErr() {
		int prevIndex = curIndex - 1;
		if (prevIndex == -1) {
			return false;
		}
		if (myList.get(prevIndex).isGroup()) {
			if (((TokenGroup) myList.get(prevIndex)).insertErr()) {
				return true;
			}
		}
		myList.add(prevIndex, TokenMap.getParameterToken(Parameters.ERR));
		return true;
	}

}
